﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MessagingWPF.Infrastructure.Utilities;
using Microsoft.Practices.ServiceLocation;

namespace MessagingWPF.Infrastructure
{
	/// <summary>
	/// Creates <see cref="Subscription"/> instances for specified subscriber instances and types.
	/// </summary>
	public class SubscriptionAssembler : ISubscriptionAssembler
	{
		private readonly IMessageInterestCache _Cache;
		private readonly IServiceLocator _Container;

		/// <summary>
		/// Initializes a new instance of the class.
		/// </summary>
		/// <param name="container">The IoC container.</param>
		/// <param name="cache">The cache of message subscription interests.</param>
		public SubscriptionAssembler(IServiceLocator container, IMessageInterestCache cache)
		{
			Ensure.ArgumentIsNotNull(container, "container");
			Ensure.ArgumentIsNotNull(cache, "cache");

			_Container = container;
			_Cache = cache;
		}

		/// <summary>
		/// Initializes a new instance of the class.
		/// </summary>
		/// <param name="container">The IoC container.</param>
		public SubscriptionAssembler(IServiceLocator container)
			: this(container, new NullMessageInterestCache())
		{
		}

		#region ISubscriptionAssembler Members

		/// <summary>
		/// Infers all message subscriptions from the specified type.
		/// </summary>
		/// <param name="subscriberType">The type of the subscriber.</param>
		/// <returns>
		/// A collection of <see cref="Subscription"/>.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="subscriberType"/> is <c>null</c>.
		/// </exception>
		/// <remarks>
		/// Returns an empty collection if no subscriptions inferred.
		/// Inferred subscriptions are cached.
		/// </remarks>
		public IEnumerable<Subscription> GetAllSubscriptions(Type subscriberType)
		{
			Ensure.ArgumentIsNotNull(subscriberType, "subscriberType");

			return new List<Type>(InferMessageInterests(subscriberType))
				.ConvertAll<Subscription>(messageType =>
					new TypeSubscription(messageType, subscriberType, _Container));
		}

		/// <summary>
		/// Infers all message subscriptions from the specified instance.
		/// </summary>
		/// <param name="subscriber">The subscriber.</param>
		/// <returns>
		/// A collection of <see cref="Subscription"/>.
		/// </returns>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="subscriber"/> is <c>null</c>.
		/// </exception>
		/// <remarks>
		/// Returns an empty collection if no subscriptions inferred.
		/// Inferred subscriptions are cached.
		/// </remarks>
		public IEnumerable<Subscription> GetAllSubscriptions(object subscriber)
		{
			Ensure.ArgumentIsNotNull(subscriber, "subscriber");

			return new List<Type>(InferMessageInterests(subscriber.GetType()))
				.ConvertAll<Subscription>(messageType =>
					new InstanceSubscription(messageType, subscriber, SyncFactory.CurrentContext));
		}

		/// <summary>
		/// Creates a single <see cref="Subscription"/> for the specified subscriber.
		/// </summary>
		/// <typeparam name="TMessage">The type of the message.</typeparam>
		/// <param name="subscriber">The subscriber instance.</param>
		/// <returns>A <see cref="Subscription"/> instance.</returns>
		/// <exception cref="ArgumentNullException">
		/// Thrown when <paramref name="subscriber"/> is <c>null</c>.
		/// </exception>
		public Subscription GetSingleSubscription<TMessage>(ISubscriber<TMessage> subscriber)
		{
			Ensure.ArgumentIsNotNull(subscriber, "subscriber");

			return new InstanceSubscription(
				typeof (TMessage),
				subscriber,
				SyncFactory.CurrentContext);
		}

		#endregion

		/// <summary>
		/// Infers all message interests of the specified subscriber.
		/// </summary>
		/// <param name="subscriberType">Type of the subscriber.</param>
		/// <returns>
		/// A collection of message types the specified subscriber is interested in.
		/// </returns>
		public ReadOnlyCollection<Type> InferMessageInterests(Type subscriberType)
		{
			Ensure.ArgumentIsNotNull(subscriberType, "subscriberType");

			if (_Cache.Contains(subscriberType))
			{
				return _Cache.Get(subscriberType);
			}

			var messageTypes = InferMessageInterestsCore(subscriberType);
			if (messageTypes.Count > 0)
			{
				_Cache.Add(subscriberType, messageTypes);
			}

			return messageTypes.AsReadOnly();
		}

		private static List<Type> InferMessageInterestsCore(Type subscriberType)
		{
			return new List<Type>(subscriberType.GetInterfaces())
				.FindAll(interfaceType =>
					interfaceType.IsGenericType &&
						interfaceType.GetGenericTypeDefinition() == typeof (ISubscriber<>))
				.ConvertAll(subscriberInterface =>
					subscriberInterface.GetGenericArguments()[0]);
		}
	}
}